package truerefactor.refactor;

import truerefactor.graph.Accessibility;
import truerefactor.graph.AttributeNode;
import truerefactor.graph.ClassNode;
import truerefactor.graph.CodeGraph;
import truerefactor.graph.CodeNode;
import truerefactor.graph.Edge;
import truerefactor.graph.EdgeType;
import truerefactor.graph.MethodNode;

/**
 * SelfENcapsulateField - An Implementation of the Self-Encapsulate Field
 * Refactoring Technique
 * 
 * @author Isaac Griffith
 */
public class SelfEncapsulateField extends Refactoring {

    /**
     * Creates a new SelfEncapsulateField instance
     */
    public SelfEncapsulateField()
    {
        this.name = "Self-Encapsulate Field";
        this.description = "";
    }

    /*
     * (non-Javadoc)
     * @see
     * truerefactor.refactor.Refactoring#execute(truerefactor.graph.CodeNode [])
     */
    @Override
    public void execute(CodeGraph graph)
    {
        if (operands.isEmpty())
            return;

        AttributeNode field = (AttributeNode) operands.get(0);

        createMethods(graph, field);

        // 3. Make the field private
        field.setAccessibility(Accessibility.Private);

        // 4. Double check that you have caught all references
        // FIXME: Assume that this will be handled later when code generation is
        // to be used
    }

    private void createMethods(CodeGraph graph, AttributeNode field)
    {
        // 1. Create a getting and setting method for the field
        MethodNode getter = new MethodNode("get" + field.getIdentifier(), "", Accessibility.Public);
        MethodNode setter = new MethodNode("set" + field.getIdentifier(), "", Accessibility.Public);
        ClassNode parent = field.getParent();
        
        if (parent != null) // FIXME: Should never be null
        {
            boolean hasGetter = false;
            boolean hasSetter = false;

            
            for (CodeNode node : parent.getAttributes())
            {
                if (node.getIdentifier().equals(getter.getIdentifier()))
                {
                    hasGetter = true;
                }

                if (node.getIdentifier().equals(setter.getIdentifier()))
                {
                    hasSetter = true;
                }
            }

            if (!hasGetter)
            {
                graph.insert(getter);
                graph.insert(new Edge(parent, getter, EdgeType.Method));
                getter.addAssociatedAttribute(field);
                field.addAssociatedClassMethod(getter);
                getter.setParentClass(parent);
            }

            if (!hasSetter)
            {
                graph.insert(setter);
                graph.insert(new Edge(parent, setter, EdgeType.Method));
                setter.addAssociatedAttribute(field);
                field.addAssociatedClassMethod(setter);
                setter.setParentClass(parent);
            }
        }
        
        replaceReferences(field, getter, setter);
    }
    
    private void replaceReferences(AttributeNode field, MethodNode getter, MethodNode setter)
    {
        // 2. Replace all references to the source field with references to the
        // appropriate method on the target.
        // a. For accesses to the variable, replace the reference with a call to
        // the target object's getting method; for assignments, replace the
        // reference with a call to the setting method.
        // b. If the field is not private, look in all the subclasses of the
        // source for references.
        for (MethodNode assoc : field.getAssociatedMethods())
        {
            double val = Math.random();
            if (val < .5)
            {
                assoc.addOtherMethod(getter);
            }
            else
            {
                assoc.addOtherMethod(setter);
            }

            assoc.removeAssociatedAttribute(field);
        }

        for (MethodNode other : field.getOtherMethods())
        {
            double val = Math.random();
            if (val < .5)
            {
                other.addOtherMethod(getter);
            }
            else
            {
                other.addOtherMethod(setter);
            }

            other.removeAssociatedAttribute(field);
        }

        field.getOtherMethods().clear();
        field.getAssociatedMethods().clear();

        field.addAssociatedClassMethod(setter);
        field.addAssociatedClassMethod(getter);
    }
}
